package ru.hh.team02.chat.client;

import ru.hh.team02.chat.messaging.Message;
import ru.hh.team02.chat.messaging.SendingQueue;
import ru.hh.team02.chat.server.Services;
import ru.hh.team02.chat.utils.StringSocket;

import java.io.IOException;
import java.net.Socket;
// XXX [Oleg] Please, add docs to the methods when you create ones.
// Without them the code is really really difficult to read and comprehend.
/**
 * class fully responsible for communicating with client computer
 */
public class Client implements Runnable {

    private StringSocket socket;
    private String name = "";
    private final SendingQueue sendingQueue = new SendingQueue(1, this);
    private MessageHandler messageHandler = null;

    /**
     * Create empty client for testing purposes.
     */
    public Client(String name) {
        this.name = name;
    }

    public Client(Socket socket) {
        this.socket = new StringSocket(socket);
    }

    public String getName() {
        return name;
    }

    // XXX [MY] Unused method
    // XXX [Oleg] Suggest deleting
    public String getRoom() {
        return Services.REGISTRY.getClientRoom(getName());
    }

    @Override
    public void run() {
        try {
            socket.write("connected");
            Services.ADMIN_LOGGER.write("New client has connected");

            // Register client in the registry, with his sendingQueue sending switched off.
            // From now on the new messages will be added to the queue, but won't be sent.
            registerClient();

            // Bind a message handler to the client
            messageHandler = new MessageHandler(getName());

            // Add all undelivered messages to the beginning of sending queue from the DB.
            Services.MESSENGER.sendUndeliveredMessages(getName());

            // Start sender threads
            sendingQueue.start();

            //XXX [MY] Variable 'message' initializer ""  is redundant
            String message = "";
            do {
                message = socket.read();
                handleMessage(message);
            } while (!message.equals("exit"));
            Services.ADMIN_LOGGER.write("Client \"" + this.name
                    + "\" has disconnected");
        } catch (IOException e) {
            Services.ADMIN_LOGGER.write("client went down");
        } catch (Exception e) {
            // XXX [Oleg] Shouldn't it go to the logger? Sonar marks all this stuff (which in fact looks ugly)
            e.printStackTrace();
        } finally {
            sendingQueue.terminate();
            Services.REGISTRY.deleteClient(this);
            socket.close();
        }
    }

    /**
     * Send message to client computer.
     */
    public void sendMessageToClientComputer(String message) {
        try {
            socket.write(message);
        } catch (IOException e) {
             // TODO [MY] Empty catch block
            // XXX [Oleg] Awful! (Tell me please later, how you've solved this, as I haven't decided
            // how to handle this. Maybe we should just log this?
        }
    }

    /**
     *  Process unhandled message
     *  @param message Raw text received from socket
     */
    private void handleMessage(String message) {
        if (message.startsWith("change")) {
            messageHandler.changeRoom(message);
        } else if (message.startsWith("pm")) {
            messageHandler.sendPersonalMessage(message);
        } else if (message.startsWith("ok")) {
            messageHandler.markMessageInDb(message);
        } else {
            messageHandler.sendMessageToRoom(message);
        }
    }


    // ------------------------------------------ AUTHENTIFICATION METHODS

    /**
     * Perform beginning negotiations with the client.
     * Login them, determine the room and register in ClientRegistry.
     */
    private void registerClient() throws IOException {
        String name = "";
        String password = "";
        // XXX [Oleg] Shouldn't we output that login-password combination is wrong?
        do {
            socket.write("Input login");
            name = socket.read();

            socket.write("Input password");
            password = socket.read();
        } while (!(Services.dbService.clientExists(name) &&
                Services.dbService.getPasswordByClientName(name).equals(password)));


        this.name = name;

        Services.ADMIN_LOGGER.write("New client is \"" + this.name + "\"");
        socket.write("Ok");

        // [TODO]: if two users enter the same name, one of them will be stuck
        // forever in the next loop. maybe make an outer loop with clientRegistry condition
        // XXX [Oleg] I think we shouldn't write OK to the client until we registered them in the registry.
        // The first one to register in the registry will enter, the other will get an error message.

        // XXX [Oleg] Now the user should take their room from the DB (new User Story).
        // So we can put them in the default room and avoid asking the room name on the login.
        // I think that REGISTRY.addClient should now not require room as parameter.
        // And this will allow us to register the user at once after entering login data.


        String room;
        do {
            socket.write("Room: "
                    + Services.REGISTRY.getRoomNames().toString());
            socket.write("Input room name");
            room = socket.read();
        } while (!(Services.REGISTRY.hasRoom(room) &&
                Services.REGISTRY.addClient(this, room)));
        Services.ADMIN_LOGGER.write("New client entered in \"" + room + "\"");
        socket.write("Ok");

    }

    public boolean add(Message message) {
        return sendingQueue.add(message);

    }

}
